package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/html"
)

// QwenConfig Qwen API配置
type QwenConfig struct {
	APIKey      string  `json:"apiKey"`
	BaseURL     string  `json:"baseUrl"`
	Model       string  `json:"model"`
	Temperature float64 `json:"temperature"`
}

// QwenRequest API请求结构
type QwenRequest struct {
	Model       string    `json:"model"`
	Messages    []Message `json:"messages"`
	Temperature float64   `json:"temperature"`
	MaxTokens   int       `json:"max_tokens"`
}

// Message 消息结构
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

// QwenResponse API响应结构
type QwenResponse struct {
	Choices []Choice `json:"choices"`
}

type Choice struct {
	Message Message `json:"message"`
}

// LLMResult LLM处理结果
type LLMResult struct {
	Title       string   `json:"title"`
	Category    string   `json:"category"`
	PublishDate string   `json:"publish_date"`
	Keywords    []string `json:"keywords"`
	Summary     string   `json:"summary"`
	Content     string   `json:"content"`
	QAPairs     []QAPair `json:"qa_pairs"`
}

// QAPair 问答对
type QAPair struct {
	Question   string `json:"question"`
	Answer     string `json:"answer"`
	Type       string `json:"type"`
	Difficulty string `json:"difficulty"`
}

// ProcessedDocument 处理后的文档
type ProcessedDocument struct {
	ID          string                 `json:"id"`
	Title       string                 `json:"title"`
	Category    string                 `json:"category"`
	Subcategory string                 `json:"subcategory"`
	PublishDate string                 `json:"publish_date"`
	SourcePath  string                 `json:"source_path"`
	URL         string                 `json:"url"`
	Keywords    []string               `json:"keywords"`
	Summary     string                 `json:"summary"`
	Content     string                 `json:"content"`
	QAPairs     []QAPair               `json:"qa_pairs"`
	Language    string                 `json:"language"`
	Metadata    map[string]interface{} `json:"metadata"`
}

// TrainingData LoRA训练数据格式
type TrainingData struct {
	Instruction string `json:"instruction"`
	Input       string `json:"input"`
	Output      string `json:"output"`
	Type        string `json:"type"`
	SourceID    string `json:"source_id"`
	Title       string `json:"title"`
	Category    string `json:"category"`
}

// RAGChunk RAG数据块
type RAGChunk struct {
	ChunkID   string                 `json:"chunk_id"`
	TextChunk string                 `json:"text_chunk"`
	Metadata  map[string]interface{} `json:"metadata"`
}

// HTMLCleaner HTML清洗器
type HTMLCleaner struct {
	config          QwenConfig
	htmlBasePath    string
	outputPath      string
	categoryMapping map[string]string
	client          *http.Client
	systemPrompt    string
}

// NewHTMLCleaner 创建新的HTML清洗器
func NewHTMLCleaner(htmlBasePath, outputPath string, config QwenConfig) *HTMLCleaner {
	// 创建输出目录
	os.MkdirAll(outputPath, 0755)

	categoryMapping := map[string]string{
		"school-news":  "学校新闻",
		"xueyuan-news": "学院新闻",
		"xsdt":         "学生动态",
		"fdyw":         "佛大要闻",
		"department":   "部门信息",
		"bls":          "管理学院",
		"fzxy":         "法学院",
		"jmxy":         "经济管理学院",
		"lnxy":         "岭南学院",
	}

	systemPrompt := `你是佛山大学智能问答系统的数据处理专家。请分析HTML内容，提取结构化信息并严格按JSON格式输出。

输出要求：
1. 只输出JSON，不要任何解释文字
2. 生成3-5个高质量问答对
3. 问答对要覆盖不同类型（事实、流程、政策、时间等）

JSON格式：
{
  "title": "页面主标题",
  "category": "分类（学校新闻/学院动态/教务信息/学生服务等）",
  "publish_date": "发布日期YYYY-MM-DD",
  "keywords": ["关键词1", "关键词2"],
  "summary": "页面内容摘要",
  "content": "完整内容文本",
  "qa_pairs": [
    {
      "question": "问题",
      "answer": "详细答案",
      "type": "问题类型（事实型/流程型/政策型/时间型等）",
      "difficulty": "简单/中等/困难"
    }
  ]
}`

	// 创建失败文件保存目录
	failedDir := filepath.Join(outputPath, "failed_files")
	os.MkdirAll(failedDir, 0755)

	return &HTMLCleaner{
		config:          config,
		htmlBasePath:    htmlBasePath,
		outputPath:      outputPath,
		categoryMapping: categoryMapping,
		client:          &http.Client{Timeout: 120 * time.Second}, // 增加到120秒
		systemPrompt:    systemPrompt,
	}
}

// cleanHTMLContent 清理HTML内容
func (hc *HTMLCleaner) cleanHTMLContent(htmlContent string) (string, error) {
	doc, err := html.Parse(strings.NewReader(htmlContent))
	if err != nil {
		return "", err
	}

	var extractText func(*html.Node) string
	extractText = func(n *html.Node) string {
		if n.Type == html.TextNode {
			text := strings.TrimSpace(n.Data)
			if len(text) > 10 {
				return text + "\n"
			}
		}

		if n.Type == html.ElementNode {
			// 跳过不需要的标签
			skipTags := map[string]bool{
				"script": true, "style": true, "nav": true,
				"header": true, "footer": true, "aside": true,
			}
			if skipTags[n.Data] {
				return ""
			}

			// 处理标题标签
			if strings.HasPrefix(n.Data, "h") && len(n.Data) == 2 {
				return fmt.Sprintf("\n\n[%s]%s[/%s]\n", strings.ToUpper(n.Data), getTextContent(n), strings.ToUpper(n.Data))
			}
		}

		var result string
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			result += extractText(c)
		}
		return result
	}

	content := extractText(doc)

	// 清理多余换行
	re := regexp.MustCompile(`\n{3,}`)
	content = re.ReplaceAllString(content, "\n\n")

	// 限制长度（按字符数，不按字节）
	contentRunes := []rune(content)
	if len(contentRunes) > 16000 {
		content = string(contentRunes[:16000]) + "...[内容过长，已截断]"
	}

	return strings.TrimSpace(content), nil
}

// getTextContent 获取节点的文本内容
func getTextContent(n *html.Node) string {
	if n.Type == html.TextNode {
		return n.Data
	}
	var result string
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		result += getTextContent(c)
	}
	return strings.TrimSpace(result)
}

// callQwenLLMWithResponse 调用Qwen LLM API并返回原始响应
func (hc *HTMLCleaner) callQwenLLMWithResponse(content string) (*LLMResult, string, error) {
	request := QwenRequest{
		Model:       hc.config.Model,
		Temperature: hc.config.Temperature,
		MaxTokens:   16000, // 增加到16000，确保足够生成完整JSON
		Messages: []Message{
			{Role: "system", Content: hc.systemPrompt},
			{Role: "user", Content: fmt.Sprintf("请分析以下HTML内容并返回JSON格式结果：\n\n%s", content)},
		},
	}

	jsonData, err := json.Marshal(request)
	if err != nil {
		return nil, "", err
	}

	req, err := http.NewRequest("POST", hc.config.BaseURL+"/chat/completions", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, "", err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+hc.config.APIKey)

	resp, err := hc.client.Do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return nil, "", fmt.Errorf("API request failed: %d %s", resp.StatusCode, string(body))
	}

	var qwenResp QwenResponse
	if err := json.NewDecoder(resp.Body).Decode(&qwenResp); err != nil {
		return nil, "", err
	}

	if len(qwenResp.Choices) == 0 {
		return nil, "", fmt.Errorf("no response choices")
	}

	llmOutput := qwenResp.Choices[0].Message.Content

	// 解析JSON输出
	result, parseErr := hc.parseJSONOutput(llmOutput)
	return result, llmOutput, parseErr
}

// callQwenLLM 调用Qwen LLM API (保持向后兼容)
func (hc *HTMLCleaner) callQwenLLM(content string) (*LLMResult, error) {
	result, _, err := hc.callQwenLLMWithResponse(content)
	return result, err
}

// parseJSONOutput 解析LLM的JSON输出
func (hc *HTMLCleaner) parseJSONOutput(output string) (*LLMResult, error) {
	// 清理可能的markdown包装
	re := regexp.MustCompile("```json\\n?(.*?)\\n?```")
	matches := re.FindStringSubmatch(output)
	if len(matches) > 1 {
		output = matches[1]
	} else {
		// 尝试找到JSON开始和结束位置
		start := strings.Index(output, "{")
		end := strings.LastIndex(output, "}")
		if start != -1 && end != -1 {
			output = output[start : end+1]
		}
	}

	// 清理尾随逗号
	re = regexp.MustCompile(`,(\s*[}\]])`)
	output = re.ReplaceAllString(output, "$1")

	var result LLMResult
	if err := json.Unmarshal([]byte(output), &result); err != nil {
		// 安全地截取错误信息显示内容
		outputRunes := []rune(output)
		maxLen := min(200, len(outputRunes))
		return nil, fmt.Errorf("JSON parsing failed: %v, content: %s", err, string(outputRunes[:maxLen]))
	}

	return &result, nil
}

// processHTMLFile 处理单个HTML文件（带重试机制）
func (hc *HTMLCleaner) processHTMLFile(filePath string) (*ProcessedDocument, error) {
	content, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}

	// 文件太小跳过
	if len(content) < 500 {
		return nil, fmt.Errorf("file too small")
	}

	// 清理HTML内容
	cleanedContent, err := hc.cleanHTMLContent(string(content))
	if err != nil {
		return nil, err
	}

	if len(cleanedContent) < 100 {
		return nil, fmt.Errorf("cleaned content too small")
	}

	// 调用LLM分析（带重试）
	llmResult, lastResponse, err := hc.callQwenLLMWithRetry(cleanedContent, 5)
	if err != nil {
		// 保存失败文件信息
		hc.saveFailedFile(filePath, cleanedContent, lastResponse, err)
		return nil, err
	}

	// 构建完整文档
	return hc.buildProcessedDocument(llmResult, filePath)
}

// callQwenLLMWithRetry 带重试机制的LLM调用，返回结果和最后一次LLM响应
func (hc *HTMLCleaner) callQwenLLMWithRetry(content string, _ int) (*LLMResult, string, error) {
	var lastErr error
	var lastLLMResponse string
	originalModel := hc.config.Model    // 保存原始模型
	fallbackModel := "qwen-plus-latest" // 备用模型

	// 总共尝试3次：前2次用原始模型，后1次用备用模型
	totalAttempts := 3
	modelSwitched := false

	for attempt := 1; attempt <= totalAttempts; attempt++ {
		// 如果前2次失败，从第3次开始切换到备用模型
		if attempt > 2 && !modelSwitched {
			hc.config.Model = fallbackModel
			modelSwitched = true
			fmt.Printf("🔄 前2次重试失败，切换到备用模型: %s\n", fallbackModel)
		}

		result, llmResponse, err := hc.callQwenLLMWithResponse(content)
		lastLLMResponse = llmResponse // 保存最后一次响应

		if err == nil {
			// 成功后恢复原始模型
			hc.config.Model = originalModel
			return result, lastLLMResponse, nil
		}

		lastErr = err

		// 确定当前使用的模型和重试次数显示
		var retryMsg string
		if attempt <= 2 {
			retryMsg = fmt.Sprintf("第%d/2次(原始模型: %s)", attempt, originalModel)
		} else {
			retryMsg = fmt.Sprintf("第%d/1次(备用模型: %s)", attempt-2, fallbackModel)
		}

		// 检查是否是超时错误
		if strings.Contains(err.Error(), "context deadline exceeded") || strings.Contains(err.Error(), "timeout") {
			// 超时错误，等待较长时间
			waitTime := time.Duration(attempt*3) * time.Second
			fmt.Printf("请求超时，等待%v后重试 (%s)...\n", waitTime, retryMsg)
			time.Sleep(waitTime)
		} else if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "limit_requests") {
			// 限流错误，等待更长时间
			waitTime := time.Duration(attempt*2) * time.Second
			fmt.Printf("API限流，等待%v后重试 (%s)...\n", waitTime, retryMsg)
			time.Sleep(waitTime)
		} else if strings.Contains(err.Error(), "JSON parsing failed") {
			// JSON解析失败，等待短时间
			waitTime := time.Duration(attempt) * time.Second
			fmt.Printf("JSON解析失败，等待%v后重试 (%s)...\n", waitTime, retryMsg)
			time.Sleep(waitTime)
		} else {
			// 其他错误，短时间重试
			waitTime := 500 * time.Millisecond * time.Duration(attempt)
			fmt.Printf("API调用失败，等待%v后重试 (%s): %v\n", waitTime, retryMsg, err)
			time.Sleep(waitTime)
		}
	}

	// 恢复原始模型
	hc.config.Model = originalModel
	return nil, lastLLMResponse, fmt.Errorf("重试%d次后仍失败(包括1次备用模型重试): %v", totalAttempts, lastErr)
}

// saveFailedFile 保存处理失败的文件信息
func (hc *HTMLCleaner) saveFailedFile(filePath string, cleanedContent string, lastLLMResponse string, err error) {
	fileName := filepath.Base(filePath)
	failedFileName := fmt.Sprintf("FAILED_%s_%d.txt",
		strings.TrimSuffix(fileName, filepath.Ext(fileName)),
		time.Now().Unix())

	failedFilePath := filepath.Join(hc.outputPath, "failed_files", failedFileName)

	failedContent := fmt.Sprintf(`处理失败文件记录
===================
文件名: %s
失败时间: %s
错误信息: %s

最后一次LLM响应内容:
===================
%s

原始清理后的HTML内容:
==================
%s

手动处理说明:
=============
1. 请检查上面的LLM响应内容是否为有效JSON
2. 如果JSON不完整，请手动补全
3. 确保JSON包含以下必需字段：
   - title: 页面标题
   - category: 分类
   - publish_date: 发布日期 (YYYY-MM-DD格式)
   - keywords: 关键词数组
   - summary: 内容摘要
   - content: 完整内容
   - qa_pairs: 问答对数组
4. 处理完成后可以手动添加到数据集中

原始文件路径: %s
`, fileName, time.Now().Format("2006-01-02 15:04:05"), err.Error(), lastLLMResponse, cleanedContent, filePath)

	if writeErr := os.WriteFile(failedFilePath, []byte(failedContent), 0644); writeErr != nil {
		fmt.Printf("⚠️ 保存失败文件记录出错: %v\n", writeErr)
	} else {
		fmt.Printf("💾 已保存失败文件记录: %s\n", failedFileName)
	}
}

// buildProcessedDocument 构建处理后的文档
func (hc *HTMLCleaner) buildProcessedDocument(llmResult *LLMResult, filePath string) (*ProcessedDocument, error) {
	// 计算相对路径
	relPath, err := filepath.Rel(hc.htmlBasePath, filePath)
	if err != nil {
		return nil, err
	}

	// 确定分类
	category, subcategory := hc.determineCategory(relPath)

	// 生成文档ID
	hash := md5.Sum([]byte(relPath))
	docID := fmt.Sprintf("%s_%x", category, hash[:6])

	// 构建URL
	url := fmt.Sprintf("https://www.fosu.edu.cn/%s", strings.TrimSuffix(relPath, ".html"))

	doc := &ProcessedDocument{
		ID:          docID,
		Title:       llmResult.Title,
		Category:    category,
		Subcategory: subcategory,
		PublishDate: llmResult.PublishDate,
		SourcePath:  relPath,
		URL:         url,
		Keywords:    llmResult.Keywords,
		Summary:     llmResult.Summary,
		Content:     llmResult.Content,
		QAPairs:     llmResult.QAPairs,
		Language:    "zh-CN",
		Metadata: map[string]interface{}{
			"extraction_timestamp": time.Now().Format(time.RFC3339),
			"processing_version":   "go_llm_v1.0",
			"llm_model":            hc.config.Model,
			"api_provider":         "Qwen",
			"total_qa_pairs":       len(llmResult.QAPairs),
		},
	}

	return doc, nil
}

// determineCategory 根据文件路径确定分类
func (hc *HTMLCleaner) determineCategory(filePath string) (string, string) {
	parts := strings.Split(filePath, "/")

	for _, part := range parts {
		if subcategory, exists := hc.categoryMapping[part]; exists {
			return part, subcategory
		}
	}

	return "other", "其他"
}

// generateTrainingData 生成LoRA训练数据
func (hc *HTMLCleaner) generateTrainingData(docs []ProcessedDocument) []TrainingData {
	var trainingData []TrainingData

	for _, doc := range docs {
		for _, qa := range doc.QAPairs {
			// 构建更具体的instruction，包含文档标题和分类信息
			instruction := fmt.Sprintf("你是佛山大学的智能问答助手。以下问题来自《%s》（%s），请根据文档内容准确回答。",
				doc.Title, doc.Subcategory)

			trainingData = append(trainingData, TrainingData{
				Instruction: instruction,
				Input:       qa.Question,
				Output:      qa.Answer,
				Type:        qa.Type,
				SourceID:    doc.ID,
				Title:       doc.Title,
				Category:    doc.Subcategory,
			})
		}
	}

	return trainingData
}

// generateRAGChunks 生成RAG数据块
func (hc *HTMLCleaner) generateRAGChunks(docs []ProcessedDocument) []RAGChunk {
	var chunks []RAGChunk
	chunkSize := 512

	for _, doc := range docs {
		// 为每个文档的内容创建块
		content := doc.Content
		contentRunes := []rune(content) // 转换为rune切片，避免切断中文字符

		if len(contentRunes) > chunkSize {
			// 按字符数分块，不会切断UTF-8字符
			chunkIndex := 0
			for i := 0; i < len(contentRunes); i += chunkSize {
				end := i + chunkSize
				if end > len(contentRunes) {
					end = len(contentRunes)
				}

				chunk := RAGChunk{
					ChunkID:   fmt.Sprintf("%s_chunk_%d", doc.ID, chunkIndex),
					TextChunk: string(contentRunes[i:end]),
					Metadata: map[string]interface{}{
						"source_doc":  doc.ID,
						"title":       doc.Title,
						"category":    doc.Category,
						"chunk_type":  "content",
						"chunk_index": chunkIndex,
						"source_url":  doc.URL,
					},
				}
				chunks = append(chunks, chunk)
				chunkIndex++
			}
		} else {
			chunk := RAGChunk{
				ChunkID:   fmt.Sprintf("%s_chunk_0", doc.ID),
				TextChunk: string(contentRunes),
				Metadata: map[string]interface{}{
					"source_doc":  doc.ID,
					"title":       doc.Title,
					"category":    doc.Category,
					"chunk_type":  "content",
					"chunk_index": 0,
					"source_url":  doc.URL,
				},
			}
			chunks = append(chunks, chunk)
		}

		// 为每个问答对创建独立的块
		for i, qa := range doc.QAPairs {
			qaPair := fmt.Sprintf("问题: %s\n答案: %s", qa.Question, qa.Answer)
			chunk := RAGChunk{
				ChunkID:   fmt.Sprintf("%s_qa_%d", doc.ID, i),
				TextChunk: qaPair,
				Metadata: map[string]interface{}{
					"source_doc": doc.ID,
					"title":      doc.Title,
					"category":   doc.Category,
					"chunk_type": "qa_pair",
					"qa_type":    qa.Type,
					"difficulty": qa.Difficulty,
					"source_url": doc.URL,
				},
			}
			chunks = append(chunks, chunk)
		}
	}

	return chunks
}

// testQwenConnection 测试Qwen API连接
func (hc *HTMLCleaner) testQwenConnection() error {
	request := QwenRequest{
		Model:       hc.config.Model,
		Temperature: hc.config.Temperature,
		MaxTokens:   50,
		Messages: []Message{
			{Role: "user", Content: "测试连接，请回复：连接成功"},
		},
	}

	jsonData, err := json.Marshal(request)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", hc.config.BaseURL+"/chat/completions", bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+hc.config.APIKey)

	resp, err := hc.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("连接失败: %d %s", resp.StatusCode, string(body))
	}

	fmt.Println("✅ Qwen API连接测试成功")
	return nil
}

// ProcessAllFiles 处理所有HTML文件
func (hc *HTMLCleaner) ProcessAllFiles(maxFiles int, concurrency int) error {
	// 测试API连接
	if err := hc.testQwenConnection(); err != nil {
		return fmt.Errorf("API连接测试失败: %v", err)
	}

	// 查找所有HTML文件
	var htmlFiles []string
	err := filepath.Walk(hc.htmlBasePath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if strings.HasSuffix(strings.ToLower(path), ".html") {
			htmlFiles = append(htmlFiles, path)
		}
		return nil
	})
	if err != nil {
		return err
	}

	fmt.Printf("找到 %d 个HTML文件\n", len(htmlFiles))

	if maxFiles > 0 && maxFiles < len(htmlFiles) {
		htmlFiles = htmlFiles[:maxFiles]
		fmt.Printf("限制处理前 %d 个文件进行测试\n", maxFiles)
	}

	// 并发处理文件
	var docs []ProcessedDocument
	var mu sync.Mutex
	var wg sync.WaitGroup

	// 创建带缓冲的channel控制并发数
	semaphore := make(chan struct{}, concurrency)

	processedCount := 0
	validCount := 0
	saveInterval := 5 // 每5个有效文件更新主文件

	for i, htmlFile := range htmlFiles {
		wg.Add(1)
		go func(file string, index int) {
			defer wg.Done()
			semaphore <- struct{}{}        // 获取信号量
			defer func() { <-semaphore }() // 释放信号量

			doc, err := hc.processHTMLFile(file)

			mu.Lock()
			processedCount++
			if processedCount%10 == 0 {
				fmt.Printf("已处理 %d/%d 个文件, 有效文件: %d\n", processedCount, len(htmlFiles), validCount)
			}

			if err != nil {
				fmt.Printf("处理文件失败 %s: %v\n", filepath.Base(file), err)
			} else {
				docs = append(docs, *doc)
				validCount++
				fmt.Printf("✅ 成功处理: %s\n", filepath.Base(file))

				// 每5个有效文件更新主文件
				if validCount%saveInterval == 0 {
					fmt.Printf("📄 达到%d个有效文件，更新主文件...\n", validCount)

					go func(currentDocs []ProcessedDocument) {
						// 异步保存，不阻塞主流程，保存所有已处理文档
						trainingData := hc.generateTrainingData(currentDocs)
						ragChunks := hc.generateRAGChunks(currentDocs)
						if err := hc.saveMainFiles(currentDocs, trainingData, ragChunks); err != nil {
							fmt.Printf("主文件更新失败: %v\n", err)
						} else {
							fmt.Printf("✅ 主文件更新成功 (%d个文档)\n", len(currentDocs))
						}
					}(append([]ProcessedDocument(nil), docs...)) // 复制切片避免并发问题
				}
			}
			mu.Unlock()

			// 添加延迟避免API过载
			time.Sleep(800 * time.Millisecond)
		}(htmlFile, i)
	}

	wg.Wait()

	fmt.Printf("处理完成！共处理 %d 个有效文件\n", len(docs))

	// 生成训练数据和RAG数据
	trainingData := hc.generateTrainingData(docs)
	ragChunks := hc.generateRAGChunks(docs)

	// 保存数据
	if err := hc.saveData(docs, trainingData, ragChunks); err != nil {
		return err
	}

	fmt.Printf("数据保存完成！\n")
	fmt.Printf("- 结构化文档: %d 个\n", len(docs))
	fmt.Printf("- 训练数据对: %d 个\n", len(trainingData))
	fmt.Printf("- RAG数据块: %d 个\n", len(ragChunks))

	return nil
}

// saveData 保存处理后的数据
func (hc *HTMLCleaner) saveData(docs []ProcessedDocument, trainingData []TrainingData, ragChunks []RAGChunk) error {
	// 保存结构化文档
	if err := hc.saveJSON(docs, "foshan_university_go.json"); err != nil {
		return err
	}

	// 保存LoRA训练数据
	if err := hc.saveJSON(trainingData, "foshan_university_training_go.json"); err != nil {
		return err
	}

	// 保存RAG数据块
	if err := hc.saveJSON(ragChunks, "foshan_university_rag_go.json"); err != nil {
		return err
	}

	// 生成统计报告
	if err := hc.generateReport(docs, trainingData, ragChunks); err != nil {
		return err
	}

	return nil
}

// saveMainFiles 保存主文件（包含所有数据的完整文件）
func (hc *HTMLCleaner) saveMainFiles(docs []ProcessedDocument, trainingData []TrainingData, ragChunks []RAGChunk) error {
	// 更新主文件（包含所有文档）
	if err := hc.saveJSON(docs, "foshan_university_go.json"); err != nil {
		return err
	}
	if err := hc.saveJSON(trainingData, "foshan_university_training_go.json"); err != nil {
		return err
	}
	if err := hc.saveJSON(ragChunks, "foshan_university_rag_go.json"); err != nil {
		return err
	}

	fmt.Printf("📄 主文件已更新: %d个文档, %d个训练对, %d个RAG块\n", len(docs), len(trainingData), len(ragChunks))
	return nil
}

// saveJSON 保存JSON文件
func (hc *HTMLCleaner) saveJSON(data interface{}, filename string) error {
	file, err := os.Create(filepath.Join(hc.outputPath, filename))
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	encoder.SetEscapeHTML(false)
	return encoder.Encode(data)
}

// generateReport 生成处理报告
func (hc *HTMLCleaner) generateReport(docs []ProcessedDocument, trainingData []TrainingData, ragChunks []RAGChunk) error {
	categoryStats := make(map[string]int)
	qaTypeStats := make(map[string]int)

	for _, doc := range docs {
		categoryStats[doc.Category]++
		for _, qa := range doc.QAPairs {
			qaTypeStats[qa.Type]++
		}
	}

	report := map[string]interface{}{
		"processing_summary": map[string]interface{}{
			"total_documents":      len(docs),
			"total_training_pairs": len(trainingData),
			"total_rag_chunks":     len(ragChunks),
			"processing_time":      time.Now().Format(time.RFC3339),
			"llm_model":            hc.config.Model,
		},
		"category_distribution": categoryStats,
		"qa_type_distribution":  qaTypeStats,
		"samples": map[string]interface{}{
			"document_sample":   docs[:min(3, len(docs))],
			"qa_samples":        trainingData[:min(5, len(trainingData))],
			"rag_chunk_samples": ragChunks[:min(3, len(ragChunks))],
		},
	}

	return hc.saveJSON(report, "go_processing_report.json")
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// func main() {
// 	// 读取配置
// 	config := QwenConfig{
// 		APIKey:      "sk-7348ae2c031147bbbe208a10e4968569",
// 		BaseURL:     "https://dashscope.aliyuncs.com/compatible-mode/v1",
// 		Model:       "qwen-flash-2025-07-28",
// 		Temperature: 0.5,
// 	}

// 	htmlBasePath := "../佛大官网的html静态页面"
// 	outputPath := "go_cleaned_data"

// 	cleaner := NewHTMLCleaner(htmlBasePath, outputPath, config)

// 	// 处理命令行参数
// 	maxFiles := 10   // 默认处理10个文件
// 	concurrency := 5 // 并发数（平衡速度与限流）

// 	if len(os.Args) > 1 {
// 		switch os.Args[1] {
// 		case "--full":
// 			maxFiles = 0 // 处理所有文件
// 			fmt.Println("开始处理全部文件...")
// 		case "--test":
// 			maxFiles = 5
// 			fmt.Println("开始测试处理(前5个文件)...")
// 		default:
// 			fmt.Println("开始默认处理(前10个文件)...")
// 			fmt.Println("使用 --full 处理全部文件，--test 处理前5个文件")
// 		}
// 	}

// 	// 开始处理
// 	if err := cleaner.ProcessAllFiles(maxFiles, concurrency); err != nil {
// 		log.Fatalf("处理失败: %v", err)
// 	}

// 	fmt.Println("🎉 Go版本LLM数据清洗完成！")
// }
